package cn.jtcoding.springframework.aop.proxy;

import cn.jtcoding.springframework.aop.aspect.AspectInfo;
import lombok.extern.slf4j.Slf4j;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

@Slf4j
public abstract class AbstractDynamicProxy implements MethodInterceptor, InvocationHandler {

    private final Class<?> targetClass;
    protected Object targetObj;
    protected List<AspectInfo> sortAspectInfoList;

    public AbstractDynamicProxy(Object targetObj, List<AspectInfo> aspectInfoList) {
        this.targetObj = targetObj;
        this.targetClass = targetObj.getClass();
        this.sortAspectInfoList = sortAspectInfoList(aspectInfoList);
    }

    protected List<AspectInfo> sortAspectInfoList(List<AspectInfo> aspectInfoList) {
        if (aspectInfoList == null || aspectInfoList.isEmpty()) {
            return Collections.emptyList();
        }
        aspectInfoList.sort(Comparator.comparingInt(AspectInfo::getOrderIndex));
        return aspectInfoList;
    }

    protected void invokeAfterThrowingAdvices(Method method, Object[] args, Throwable e) throws Throwable {
        for (int i = sortAspectInfoList.size() - 1; i >= 0; i--) {
            AspectInfo aspectInfo = sortAspectInfoList.get(i);
            log.debug("invokeAfterThrowingAdvices --> targetClass: {}, aspectInfo: {}", targetClass, aspectInfo);
            aspectInfo.getDefaultAspect().afterThrowing(targetClass, method, args, e);
        }
    }

    protected Object invokeAfterReturningAdvices(Method method, Object[] args, Object returnVal) throws Throwable {
        Object val = returnVal;
        for (int i = sortAspectInfoList.size() - 1; i >= 0; i--) {
            AspectInfo aspectInfo = sortAspectInfoList.get(i);
            log.debug("invokeAfterReturningAdvices --> targetClass: {}, aspectInfo: {}", targetClass, aspectInfo);
            val = aspectInfo.getDefaultAspect().afterReturning(targetClass, method, args, returnVal);
        }
        return val;
    }

    protected void invokeBeforeAdvices(Method method, Object[] args) throws Throwable {
        for (AspectInfo aspectInfo : sortAspectInfoList) {
            log.debug("invokeBeforeAdvices --> targetClass: {}, aspectInfo: {}", targetClass, aspectInfo);
            aspectInfo.getDefaultAspect().before(targetClass, method, args);
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (emptyAspect()) return method.invoke(targetObj, args);

        Object returnVal = null;
        // 1. 执行所有before方法
        invokeBeforeAdvices(method, args);
        // 2. 执行目标方法
        try {
            returnVal = method.invoke(targetObj, args);
            // 3. 执行afterReturning方法
            returnVal = invokeAfterReturningAdvices(method, args, returnVal);
        } catch (Throwable e) {
            // 4. 执行afterThrowing方法
            invokeAfterThrowingAdvices(method, args, e);
        }
        return returnVal;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        if (emptyAspect()) return methodProxy.invoke(targetObj, args);

        Object returnVal = null;
        // 1. 执行所有before方法
        invokeBeforeAdvices(method, args);
        // 2. 执行目标方法
        try {
            returnVal = methodProxy.invoke(targetObj, args);
            // 3. 执行afterReturning方法
            returnVal = invokeAfterReturningAdvices(method, args, returnVal);
        } catch (Throwable e) {
            // 4. 执行afterThrowing方法
            invokeAfterThrowingAdvices(method, args, e);
        }
        return returnVal;
    }

    protected boolean emptyAspect() {
        return sortAspectInfoList == null || sortAspectInfoList.isEmpty();
    }
}
